package com.ruicar.afs.cloud.bizcommon.rules.component;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruicar.afs.cloud.bizcommon.enums.rules.RuleStatus;
import com.ruicar.afs.cloud.bizcommon.rules.entity.AfsRuleInfo;
import com.ruicar.afs.cloud.bizcommon.rules.entity.AfsRuleInputMapping;
import com.ruicar.afs.cloud.bizcommon.rules.entity.AfsRuleResultMapping;
import com.ruicar.afs.cloud.bizcommon.rules.entity.AfsRuleTransExpress;
import com.ruicar.afs.cloud.bizcommon.rules.mapper.AfsRuleInputMappingMapper;
import com.ruicar.afs.cloud.bizcommon.rules.mapper.AfsRuleResultMappingMapper;
import com.ruicar.afs.cloud.bizcommon.rules.mapper.AfsRuleTransExpressMapper;
import com.ruicar.afs.cloud.bizcommon.rules.service.AfsRuleInfoService;
import com.ruicar.afs.cloud.common.core.service.ServiceInfo;
import com.ruicar.afs.cloud.common.core.util.SpringContextHolder;
import com.ruicar.afs.cloud.common.data.lock.anno.Lock;
import com.ruicar.afs.cloud.common.datasync.componts.SyncHelper;
import com.ruicar.afs.cloud.common.rules.RuleCaches;
import com.ruicar.afs.cloud.common.rules.constants.RuleConstants;
import com.ruicar.afs.cloud.common.rules.constants.ValueType;
import com.ruicar.afs.cloud.common.rules.dto.Rule;
import com.ruicar.afs.cloud.common.rules.dto.RuleAtomMapping;
import com.ruicar.afs.cloud.common.rules.dto.RuleGroup;
import com.ruicar.afs.cloud.common.rules.dto.RuleResult;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
@Slf4j(topic = "afsRule")
@AllArgsConstructor
public class RulePublishComponent implements ApplicationListener<ApplicationReadyEvent> {
    private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
    private final AfsRuleInfoService afsRuleInfoService;
    private final AfsRuleResultMappingMapper afsRuleResultMappingMapper;
    private final AfsRuleInputMappingMapper afsRuleInputMappingMapper;
    private final AfsRuleTransExpressMapper afsRuleTransExpressMapper;
    private final StringRedisTemplate stringRedisTemplate;
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        schedule();
    }

    @Lock(keyConstant = "lock-afs-rule-publish", keys = "#serviceName")
    public void publishRule(String serviceName,boolean forceSync) {
        try {
            //加载已激活规则ID
            List<AfsRuleInfo> afsRuleInfos = afsRuleInfoService.
                    list(Wrappers.<AfsRuleInfo>lambdaQuery().eq(AfsRuleInfo::getStatus, RuleStatus.ACTIVE));
            Map<String, RuleGroup> ruleGroupMap = new HashMap<>();
            Set<String> activeRules = new HashSet<>();
            Set<String> warnRuleNo = new HashSet<>();
            for (AfsRuleInfo afsRuleInfo : afsRuleInfos) {
                //规则已经到期
                if (afsRuleInfo.getActiveEndDate() != null && DateUtil.parse(afsRuleInfo.getActiveEndDate(), DatePattern.PURE_DATETIME_PATTERN).isBefore(DateUtil.date())) {
                    log.debug("规则{}已经到期修改规则状态为失效", afsRuleInfo.getRuleNo());
                    //先反激活再失效
                    afsRuleInfoService.deActiveRuleWithoutPublish(afsRuleInfo.getId());
                    afsRuleInfoService.expiredRule(afsRuleInfo.getId());
                    continue;
                }
                if (!ruleGroupMap.containsKey(afsRuleInfo.getRuleGroup())) {
                    ruleGroupMap.put(afsRuleInfo.getRuleGroup(), new RuleGroup());
                    ruleGroupMap.get(afsRuleInfo.getRuleGroup()).setRules(new ArrayList<>());
                }
                if(activeRules.contains(afsRuleInfo.getRuleNo())||warnRuleNo.contains(afsRuleInfo.getRuleNo())){
                    log.warn("规则编号为[{}]的规则存在两条激活规则，丢弃该条规则发布",afsRuleInfo.getRuleNo());
                    activeRules.remove(afsRuleInfo.getRuleNo());
                    warnRuleNo.add(afsRuleInfo.getRuleNo());
                    List<Rule> rules =  ruleGroupMap.get(afsRuleInfo.getRuleGroup())
                            .getRules().stream()
                            .filter(rule -> !StringUtils.equalsIgnoreCase(rule.getRuleNo(),afsRuleInfo.getRuleNo()))
                            .collect(Collectors.toList());
                    ruleGroupMap.get(afsRuleInfo.getRuleGroup()).setRules(rules);
                }else {
                    activeRules.add(afsRuleInfo.getRuleNo());
                    ruleGroupMap.get(afsRuleInfo.getRuleGroup()).getRules().add(loadRule(afsRuleInfo));
                }
            }

            //状态新建但未生效规则
            afsRuleInfos = afsRuleInfoService.
                    list(Wrappers.<AfsRuleInfo>lambdaQuery().eq(AfsRuleInfo::getStatus, RuleStatus.NEW)
                            .le(AfsRuleInfo::getActiveStartDate, DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN))
                            .orderByAsc(AfsRuleInfo::getRuleNo)
                            .orderByDesc(AfsRuleInfo::getRuleVersion)
                    );
            for (AfsRuleInfo afsRuleInfo : afsRuleInfos) {
                //激活规则，然后发布
                if (activeRules.contains(afsRuleInfo.getRuleNo())) {
                    log.warn("编号为{}的规则不同版本已经激活，不能同时激活不同版本,待激活版本{}", afsRuleInfo.getRuleNo(), afsRuleInfo.getRuleVersion());
                    continue;
                }
                activeRules.add(afsRuleInfo.getRuleNo());
                afsRuleInfoService.activeRuleWithoutPublish(afsRuleInfo.getId());
                if (!ruleGroupMap.containsKey(afsRuleInfo.getRuleGroup())) {
                    ruleGroupMap.put(afsRuleInfo.getRuleGroup(), new RuleGroup());
                    ruleGroupMap.get(afsRuleInfo.getRuleGroup()).setRules(new ArrayList<>());
                }
                ruleGroupMap.get(afsRuleInfo.getRuleGroup()).getRules().add(loadRule(afsRuleInfo));
            }
            Map<String, String> ruleCheck = new HashMap<>();
            ruleGroupMap.values().stream().forEach(group -> {
                group.getRules().stream().forEach(rule -> {
                    ruleCheck.put(rule.getRuleNo(), SecureUtil.md5(rule.getRuleExpress()));
                });
            });
            boolean ruleEqual = checkRuleEqual(ruleCheck,RuleCaches.getChecks());
            if (!ruleEqual||forceSync) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.putAll(ruleGroupMap);
                stringRedisTemplate.opsForValue().set(RuleConstants.RULE_SYNC_TIMESTAMP,System.currentTimeMillis()+"");
                SyncHelper.publishParams(RuleConstants.RULE_SYNC_KEY, jsonObject);
            } else {
                log.debug("缓存中规则与发布规则一致，跳过本次发布.........");
            }
        } catch (Throwable e) {
            log.error("规则发布失败", e);
        }
    }

    private boolean checkRuleEqual(Map<String,String> source,Map<String,String> target){
        if(source.size()!=target.size()){
            return false;
        }

        boolean rtnVal = true;
        for (Map.Entry<String, String> entry : source.entrySet()) {
            if(!target.containsKey(entry.getKey())|| !StringUtils.equalsIgnoreCase(entry.getValue(),target.get(entry.getKey()))){
                rtnVal = false;
                break;
            }
        }
        return rtnVal;
    }

    private Rule loadRule(AfsRuleInfo afsRuleInfo) {
        Rule rule = new Rule();
        rule.setRuleNo(afsRuleInfo.getRuleNo());
        rule.setRuleName(afsRuleInfo.getRuleName());
        AfsRuleResultMapping mapping = afsRuleResultMappingMapper.selectOne(Wrappers.<AfsRuleResultMapping>lambdaQuery().eq(AfsRuleResultMapping::getRuleId, afsRuleInfo.getId()));
        RuleResult ruleResult = new RuleResult();
        ruleResult.setRuleNo(afsRuleInfo.getRuleNo());
        ruleResult.setRuleName(afsRuleInfo.getRuleName());
        ruleResult.setResult(mapping == null ? true : mapping.getResult());
        rule.setResultMapping(ruleResult);

        AfsRuleTransExpress afsRuleTransExpress = afsRuleTransExpressMapper
                .selectOne(Wrappers.<AfsRuleTransExpress>lambdaQuery().eq(AfsRuleTransExpress::getRuleId, afsRuleInfo.getId()).orderByDesc(AfsRuleTransExpress::getSaveVersion));
        rule.setRuleExpress(afsRuleTransExpress.getQlExpress());

        List<RuleAtomMapping> ruleAtomMappings = afsRuleInputMappingMapper
                .selectList(Wrappers.<AfsRuleInputMapping>lambdaQuery().eq(AfsRuleInputMapping::getRuleId, afsRuleInfo.getId()))
                .stream()
                .map(afsRuleInputMapping -> {
                    RuleAtomMapping ruleAtomMapping = new RuleAtomMapping();
                    ruleAtomMapping.setValuePath(afsRuleInputMapping.getValueExpress());
                    ruleAtomMapping.setMappingName(afsRuleInputMapping.getMappingKey());
                    switch (afsRuleInputMapping.getAtomType()) {
                        case DATE:
                            ruleAtomMapping.setType(ValueType.DATE);
                            break;
                        case NUMBER:
                            ruleAtomMapping.setType(ValueType.NUMBER);
                            break;
                        default:
                            ruleAtomMapping.setType(ValueType.STRING);
                            break;
                    }
                    ruleAtomMapping.setFormat(afsRuleInputMapping.getAtomFormat());
                    ruleAtomMapping.setScale(afsRuleInputMapping.getAtomScale());
                    return ruleAtomMapping;
                }).collect(Collectors.toList());
        rule.setRuleAtomMappings(ruleAtomMappings);
        return rule;
    }

    private void schedule() {
        //每10s 检测发布处理一次规则
        //激活但到期规则置为失效
        //新建但 到期但未激活规则则置为激活
     EXECUTOR_SERVICE.scheduleWithFixedDelay(() -> {
         try {
             SpringContextHolder.getBean(RulePublishComponent.class).publishRule(ServiceInfo.getServiceName(), false);
         }catch (Throwable throwable){
             log.error("同步规则失败",throwable);
         }
        }, 2, 10, TimeUnit.SECONDS);
    }
}
